write an hp bar with one line of code

Last month pokemon sword and shield came out. Despite the controversy I was eagerly awaiting the release of these titles, but sadly I didn't get any enjoyment out of them after the short 20 hour storyline.

It did motivate me to get back to work on my own pokemon project though (or rather, start from scratch again πŸ˜‰). So here we are.

As I was working on the UI for pokemon in the party I realized that I'd need to create an hp bar for the pokemon that can be drained and restored through code. A couple of years ago I would've done this with a bunch of if statements to change the color and length of the hp bar sprite, but I knew that I'd be able to achieve a more elegant solution by using shaders.

First things first, I created a lookup table with all of the states that the hp bar can be in; green, orange, red and empty.

As you can see I will be using the x axis to get the state that the bar should be in. The y axis will be used "as is" which I use to add some nice shading at the bottom of the hpbar.

Now for the fun part! How will we translate this table into an actual hp bar?

I'll start by defining a float _Ratio which ranges from 0 to 1. At 0 the pokemon has completely fainted and at 1 it is at full health. Let's keep things simple and always sample the green for the filled part of the bar for now:

if (i.uv.x>_Ratio) {
    i.uv.x=.99;
} else {
    i.uv.x=.7;
}
return tex2D(_MainTex, i.uv);

The .99 and .7 magic numbers are just how far along in the lookup texture we are sampling. .99 is all the way at the end and will become the "empty" beige color while .7 is a little more to the left and will sample the green. At this point you'll already have a fully functioning hp bar:

But what about the other colors? You could add some more if statements to change to color depending on the value of _Ratio, but we can do better. Look at the lookup table once more:

Notice something? I've drawn it so that the color lines up with the hp percentage at which point it should trigger! (except the green, we'll have to do a bit of work to fix that).

So first let's just change the code to sample using _Ratio instead of just .7:

if (i.uv.x>_Ratio) {
    i.uv.x=.99;
} else {
    i.uv.x=_Ratio;
}
return tex2D(_MainTex, i.uv);

It kinda works, but at .75 it breaks because now it samples the empty color instead of green. Luckily this is an easy fix, just cap the result using min()

if (i.uv.x>_Ratio) {
    i.uv.x=.99;
} else {
    i.uv.x=min(_Ratio,.7);
}
return tex2D(_MainTex, i.uv);

And there we have it, a fully functional xp bar! As a bonus it also fits into a single line of code if you use a ternary expression instead of the if statement:

return tex2D(_MainTex, float2(i.uv.x > _Ratio ? .99 : min(_Ratio,.7), i.uv.y));

All done, check out this crudely made gif of the final result: